package com.ysw.http系列.config;

import lombok.Data;
import org.apache.http.client.HttpClient;
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
import org.apache.http.impl.client.DefaultServiceUnavailableRetryStrategy;
import org.apache.http.impl.client.HttpClientBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.web.client.RestTemplate;

import java.nio.charset.Charset;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * @ClassName RestTemplateConfig
 * @Description restTemplate http 调用工具
 * @Author ysw
 * @Date 2021/10/30 18:06
 */
@Configuration
public class RestTemplateConfig {
    @Bean
    RestTemplate restTemplate(ClientHttpRequestFactory clientHttpRequestFactory) {
        RestTemplate restTemplate = new RestTemplate(clientHttpRequestFactory);
        List<HttpMessageConverter<?>> messageConverters = restTemplate.getMessageConverters();
        for (HttpMessageConverter c : messageConverters) {
            if (c instanceof StringHttpMessageConverter) {
                ((StringHttpMessageConverter) c).setDefaultCharset(Charset.forName("utf-8"));
            }
        }

        return restTemplate;
    }

    /**
     * 进行配置文件引入
     * @return
     */
    @Bean
    @ConfigurationProperties(prefix = "spring.resttemplate")
    HttpClientProperties httpClientProperties() {
        return new HttpClientProperties();
    }

    @Bean
    ClientHttpRequestFactory clientHttpRequestFactory(HttpClientProperties httpClientProperties) {
        //如果不使用HttpClient的连接池，则使用restTemplate默认的SimpleClientHttpRequestFactory,底层基于HttpURLConnection
        if (!httpClientProperties.isUseHttpClientPool()) {
            SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
            factory.setConnectTimeout(httpClientProperties.getConnectTimeout());
            factory.setReadTimeout(httpClientProperties.getReadTimeout());
            return factory;
        }
        //HttpClient4.3及以上版本不手动设置HttpClientConnectionManager
        //默认就会使用连接池PoolingHttpClientConnectionManager
        HttpClient httpClient = HttpClientBuilder.create()
                /**
                 *   1.只有发生IOExecetion时才会发生重试
                 *   2.InterruptedIOException、UnknownHostException、ConnectException、SSLException，发生这4中异常不重试
                 *   3.get方法可以重试3次，post方法在socket对应的输出流没有被write并flush成功时可以重试3次。
                 *   4.读/写超时不进行重试
                 *   5.socket传输中被重置或关闭会进行重试
                 *   6.以及一些其他的IOException，暂时分析不出来。
                 */
                .setRetryHandler(new DefaultHttpRequestRetryHandler())
                // 服务不可用重试1次
                .setServiceUnavailableRetryStrategy(new DefaultServiceUnavailableRetryStrategy())
                .setMaxConnTotal(httpClientProperties.getMaxTotalConnect())
                .setMaxConnPerRoute(httpClientProperties.getMaxConnectPerRoute()).evictExpiredConnections()
                .evictIdleConnections(5000, TimeUnit.MILLISECONDS).build();
        HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient);
        factory.setConnectTimeout(httpClientProperties.getConnectTimeout());
        factory.setReadTimeout(httpClientProperties.getReadTimeout());
        factory.setConnectionRequestTimeout(httpClientProperties.getConnectionRequestTimeout());
        return factory;
    }

    /**
     * 可以通过配置文件进行配置
     */
    @Data
    class HttpClientProperties {

        /**
         * 是否使用httpclient连接池
         */
        private boolean useHttpClientPool = false;

        /**
         * 从连接池中获得一个connection的超时时间
         */
        private int connectionRequestTimeout = 3000;

        /**
         * 建立连接超时时间
         */
        private int connectTimeout = 3000;

        /**
         * 建立连接后读取返回数据的超时时间
         */
        private int readTimeout = 5000;

        /**
         * 连接池的最大连接数，0代表不限
         */
        private int maxTotalConnect = 128;

        /**
         * 每个路由的最大连接数
         */
        private int maxConnectPerRoute = 32;


        public int getMaxConnectPerRoute() {
            return maxConnectPerRoute;
        }


        public boolean isUseHttpClientPool() {
            return useHttpClientPool;
        }

    }
}